/*
 * Matthew Cramer, Jesse Martinez, Marc Mendoza 
 * CSE 325 Lab 5
 * File: MemoryManager.c
 * Description: The Memory manager manages memory blocks in backing store and physical memory
 *
 */

#include "../QueueManager/QueueManager.h"
#include "MemoryManager.h"
#include "../LRU/LRU.h"
#define TOTAL_LOGICAL_MEMORY 256
#define TOTAL_BACKING_STORE 16384

int pid = 0;
int userProcesses = 0; //max 12
int systemProcesses = 0; //max 4

//Logical Memory array and head and tail pointers
memory_block logicalMemory[TOTAL_LOGICAL_MEMORY];
int lmHead = -1;
int lmTail = -1;
int lmFreeHead = 0;
int lmFreeTail = TOTAL_LOGICAL_MEMORY-1;
int lmSize = 0;

//Backing Store array and head and tail pointers
memory_block backingStore[TOTAL_BACKING_STORE];
int bsHead = -1;
int bsTail = -1;
int bsFreeHead = 0;
int bsFreeTail = TOTAL_BACKING_STORE-1;
int bsSize = 0;

memory_block* getLogicalMemory();
memory_block* getBackingStore();
void initializeMemory();
void clearMemory();
int alloc_pt(int ptsize, int type);
int dealloc_pt(int ptID);
int page_fault (int ptID, int pageNum);
int getHead(int);
int getTail(int);
int getFreeHead(int);
int getFreeTail(int);
int getSize(int);

memory_block* getLogicalMemory()
{
	return logicalMemory;
}

memory_block* getBackingStore()
{
	return backingStore;
}

int getHead(int which)
{
    if(which==1)
        return lmHead;
    else
        return bsHead;
}

int getTail(int which)
{
    if(which==1)
        return lmTail;
    else
        return bsTail;
}

int getFreeHead(int which)
{
    if(which==1)
        return lmFreeHead;
    else
        return bsFreeHead;
}

int getFreeTail(int which)
{
    if(which==1)
        return lmFreeTail;
    else
        return bsFreeTail;
}

int getSize(int which)
{
    if(which==1)
        return lmSize;
    else
        return bsSize;
}

//reset all variables
void initializeMemory()
{
	iniFree(&lmFreeHead, &lmFreeTail, 0, logicalMemory);
	iniFree(&bsFreeHead, &bsFreeTail, 0, backingStore);
    
    page_table pagetable[1024];
    int i;
    for(i=0; i<16; i++)
    {
        pagetable[i].frame = -1;
        pagetable[i].frameType = -1;
        pagetable[i].valid = 0;
    }
    
    Enqueue(-1, 1, 0, 1, 0, 0, 0, pagetable, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory);
    lmSize++;
	pid = 0;
	userProcesses = 0;
	systemProcesses = 0;
}

//this clears out all queues
void clearMemory()
{
	iniQueue(&lmHead, &lmTail, &lmFreeHead, &lmFreeTail, &lmSize, logicalMemory, 0, TOTAL_LOGICAL_MEMORY);
	iniQueue(&bsHead, &bsTail, &bsFreeHead, &bsFreeTail, &bsSize, backingStore, 0, TOTAL_BACKING_STORE);
	
	initializeMemory();
}

//Allocation of a new page table
int alloc_pt(int ptsize, int type)
{
	//A page table is created and filled based on the size defined by the user
    page_table pagetable[1024];
    int i;
    for(i=0; i<ptsize; i++)
    {
        pagetable[i].frame = -1;
        pagetable[i].frameType = -1;
        pagetable[i].valid = 0;
    }
	
	//This allocates a page table for a user processes only if there is less than 12
	//currently running user processes and less than 16 total processes
	if((type == 1) && (userProcesses < 12) && (userProcesses + systemProcesses < 16))
	{
        //if there is space in logical memory the new page table is simply enqueued
		if(lmFreeHead != -1)
		{
            //Header page table is updated with the location of the new page table
			logicalMemory[0].ptsize++;
			logicalMemory[0].pagetable[logicalMemory[0].ptsize-1].frame = lmFreeHead;
            logicalMemory[0].pagetable[logicalMemory[0].ptsize-1].valid = 1;
            
			//new page table is enqued
			Enqueue(pid, 1, 0, 1, 1, ptsize, 0, pagetable, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory);
            lmSize++;
		}
		//LRU is called to make space to allocate space for the page table
		else
		{
			//LRU is found in order to move that frame into backing store from main memory
			int index = find(logicalMemory);
            int pageID = logicalMemory[index].ID;
            int pageNum = logicalMemory[index].data;
            logicalMemory[pageID].pagetable[pageNum].frame = bsFreeHead;
            logicalMemory[pageID].pagetable[pageNum].frameType = 2;
            logicalMemory[pageID].pagetable[pageNum].valid = 0;
			//enqueue LRU frame into the backing store
			Enqueue(logicalMemory[index].ID, 0, 0, 2, logicalMemory[index].owner, logicalMemory[index].ptsize, logicalMemory[index].data, logicalMemory[index].pagetable, &bsHead, &bsTail, &bsFreeHead, &bsFreeTail, backingStore);
			bsSize++;
			//page table pointer of victim frame is modified here
            
			//victim is removed from logical memory
			memory_block temp[1];
			Delete(index, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory, temp);
            lmSize--;
            logicalMemory[0].ptsize++;
			logicalMemory[0].pagetable[logicalMemory[0].ptsize-1].frame = lmFreeHead;
            logicalMemory[0].pagetable[logicalMemory[0].ptsize-1].valid = 1;
			//new page table is enqueued
			Enqueue(pid, 1, 0, 1, 1, ptsize, 0, pagetable, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory);
            lmSize++;

		}
		
		//increment pid and user processes counters
		pid++;
		userProcesses++;
		
		return pid-1;
	}
	//It is a system process and there is space for the process
    else if(type == 2 && (systemProcesses < 4) && ((userProcesses + systemProcesses < 16)))//is a system process
	{
		//there is space in logical memory and the page table is enqueued into logical memory
		if(lmFreeHead != -1)
		{
			//Header page table is updated with the location of the new page table
			logicalMemory[0].ptsize++;
			logicalMemory[0].pagetable[logicalMemory[0].ptsize-1].frame = lmFreeHead;
            logicalMemory[0].pagetable[logicalMemory[0].ptsize-1].valid = 1;
			
			//new page table is enqueued into logical memory
			Enqueue(pid, 1, 0, 1, 2, ptsize, 0, pagetable, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory);
            lmSize++;
		}
		//LRU is found to replace a victim frame with the new page table
		else
		{
			//LRU is found in order to move that frame into backing store from main memory
			int index = find(logicalMemory);
            int pageID = logicalMemory[index].ID;
            int pageNum = logicalMemory[index].data;
			
			//page table pointers are updated
            logicalMemory[pageID].pagetable[pageNum].frame = bsFreeHead;
            logicalMemory[pageID].pagetable[pageNum].frameType = 2;
            logicalMemory[pageID].pagetable[pageNum].valid = 0;
			
			//enqueue LRU frame into the backing store
			Enqueue(logicalMemory[index].ID, 0, 0, 2, logicalMemory[index].owner, logicalMemory[index].ptsize, logicalMemory[index].data, logicalMemory[index].pagetable, &bsHead, &bsTail, &bsFreeHead, &bsFreeTail, backingStore);
			bsSize++;
			
			//victim is removed from logical memory
			memory_block temp[1];
			Delete(index, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory, temp);
			lmSize--;
            
			//header page table pointers are updated
            logicalMemory[0].ptsize++;
			logicalMemory[0].pagetable[logicalMemory[0].ptsize-1].frame = lmFreeHead;
            logicalMemory[0].pagetable[logicalMemory[0].ptsize-1].valid = 1;
			//new page table is enqueued
			Enqueue(pid, 1, 0, 1, 2, ptsize, 0, pagetable, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory);
            lmSize++;
		}
		
		//increment pid and system processes counter
		pid++;
		systemProcesses++;
        
		return pid-1;
	}
	
	//no space available
	return -2;
}

//Page table specified by the testing interface is deallocated
int dealloc_pt(int ptID)
{
	memory_block temp[1];
    
	//the header page table is looped through until the specified page table is found
	int j = 0, found = 0;
	for(j = 0; j < logicalMemory[0].ptsize; j++)
	{
		if(found == 0)
		{
			//a pointer to a page table
			int ptPointer = logicalMemory[0].pagetable[j].frame;

			//if this page table is the one being looked for
			if(logicalMemory[ptPointer].ID == ptID)
			{
				//loop through page tables and remove all pages in memory
				int i = 0;
				//All pages are found and deleted from memory
				for(i = 0; i < logicalMemory[ptPointer].ptsize; i++)
				{
					//if in logical memory
					if(logicalMemory[ptPointer].pagetable[i].frameType == 1)
					{
						int pagePointer = logicalMemory[ptPointer].pagetable[i].frame;
						logicalMemory[pagePointer].valid = 0;
						
						Delete(pagePointer, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory, temp);
                        lmSize--;
					}
					//else is in backing store
					else if(logicalMemory[ptPointer].pagetable[i].frameType == 2)
					{

						int pagePointer = logicalMemory[ptPointer].pagetable[i].frame;
						backingStore[pagePointer].valid = 0;
						
						Delete(pagePointer, &bsHead, &bsTail, &bsFreeHead, &bsFreeTail, backingStore, temp);
                        bsSize--;
                        
					}
					//otherwise it is assumed to be in the file system and nothing needs to be done
				}
				
				//process counter is deleted depending on owner
                if(logicalMemory[ptPointer].owner == 1)
                    userProcesses--;
                else if (logicalMemory[ptPointer].owner == 2)
                    systemProcesses--;
                
				//Page table is deleted
				Delete(ptPointer, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory, temp);
                lmSize--;
                logicalMemory[0].pagetable[j].frame = -1;
                logicalMemory[0].pagetable[j].frameType = -1;
                logicalMemory[0].pagetable[j].valid = 0;
                
				//header page table is shifted to reflect deletion of the page table
                int a;
                for(a=j; a<16; a++)
                {
                    if(a==15)
                    {
                        logicalMemory[0].pagetable[a].frame = -1;
                        logicalMemory[0].pagetable[a].frameType = -1;
                        logicalMemory[0].pagetable[a].valid = 0;
                    }
                    else
                        logicalMemory[0].pagetable[a].frame = logicalMemory[0].pagetable[a+1].frame;
                        logicalMemory[0].pagetable[a].frameType = logicalMemory[0].pagetable[a+1].frameType;
                        logicalMemory[0].pagetable[a].valid = logicalMemory[0].pagetable[a+1].valid;
                }
                logicalMemory[0].ptsize--;
                found = 1;
			}
		}
	}
	
	//ptID did not exist and manager returns an error
	return found;
}

int page_fault(int ptID, int pageNum)
{
    int error =0;
    int j;
    int temp =0;
    for(j = 0; j < logicalMemory[0].ptsize; j++)
    {
        int ptPointer = logicalMemory[0].pagetable[j].frame;//index in memory
        
        if(logicalMemory[ptPointer].ID == ptID)
        {
            temp =1;
            //If the page is valid in the page table
            if(pageNum < logicalMemory[ptPointer].ptsize)
            {
                //If the page is not valid
                if(logicalMemory[ptPointer].pagetable[pageNum].valid == 0)
                {
                    //If the page is in the file system and has not been brought into memory at all
                    if(logicalMemory[ptPointer].pagetable[pageNum].frameType == -1 && lmSize < 256)
                    {

                        logicalMemory[ptPointer].pagetable[pageNum].valid = 1;
                        logicalMemory[ptPointer].pagetable[pageNum].frameType = 1;
                        logicalMemory[ptPointer].pagetable[pageNum].frame = lmFreeHead;
                        
                        Enqueue(ptPointer, 1, 0, 2, logicalMemory[ptPointer].owner, 0, pageNum, 0, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory);
                        
						logicalMemory[logicalMemory[ptPointer].pagetable[pageNum].frame].LRU++;
                        
                        lmSize++;
                    }   
                    //If the page is in the file system but there is not memory left
                    else if(logicalMemory[ptPointer].pagetable[pageNum].frameType == -1 && lmSize >= 256)
                    {
                        
                        memory_block temp[1];
                        int index = find(logicalMemory);

                        int pageID = logicalMemory[index].ID;
                        int pageNum1 = logicalMemory[index].data;
                        logicalMemory[pageID].pagetable[pageNum1].frame = bsFreeHead;
                        logicalMemory[pageID].pagetable[pageNum1].frameType = 2;
                        logicalMemory[pageID].pagetable[pageNum1].valid = 0;
                        int tempLRU = logicalMemory[index].LRU;
                        //send to backingstore
                        Enqueue(logicalMemory[index].ID, 0, 0, 2, logicalMemory[index].owner, logicalMemory[index].ptsize, logicalMemory[index].data, logicalMemory[index].pagetable, &bsHead, &bsTail, &bsFreeHead, &bsFreeTail, backingStore);
                        bsSize++;
                        
                        Delete(index, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory, temp);
                        lmSize--;
                        
                        logicalMemory[ptPointer].pagetable[pageNum].valid = 1;
                        logicalMemory[ptPointer].pagetable[pageNum].frameType = 1;
                        logicalMemory[ptPointer].pagetable[pageNum].frame = lmFreeHead;
                        
                        Enqueue(ptPointer, 1, tempLRU, 2, logicalMemory[ptPointer].owner, 0, pageNum, 0, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory);
                        
						logicalMemory[logicalMemory[ptPointer].pagetable[pageNum].frame].LRU++;
                        
                        lmSize++;
                    }
                    //If the page is in physical memory, validate it
                    else if(logicalMemory[ptPointer].pagetable[pageNum].frameType == 1)
                    {

                        logicalMemory[ptPointer].pagetable[pageNum].valid = 1;
                        logicalMemory[ptPointer].pagetable[pageNum].frameType = 1;
						logicalMemory[logicalMemory[ptPointer].pagetable[pageNum].frame].LRU++;
                    }
                    //If the page is in backing store, bring it to memory
                    else if(logicalMemory[ptPointer].pagetable[pageNum].frameType == 2)
                    {

                        memory_block temp[1];
                        if(lmSize >= 255)
                        {

                            int index = find(logicalMemory);
                            //send to backingstore
                            int pageID = logicalMemory[index].ID;
                            int pageNum1 = logicalMemory[index].data;
                            logicalMemory[pageID].pagetable[pageNum1].frame = bsFreeHead;
                            logicalMemory[pageID].pagetable[pageNum1].frameType = 2;
                            logicalMemory[pageID].pagetable[pageNum1].valid = 0;
                            int tempLRU = logicalMemory[index].LRU;
                            Enqueue(logicalMemory[index].ID, 0, 0, 2, logicalMemory[index].owner, 0, logicalMemory[index].data, logicalMemory[index].pagetable, &bsHead, &bsTail, &bsFreeHead, &bsFreeTail, backingStore);
                            bsSize++;
                            
                            Delete(index, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory, temp);
                            lmSize--;
                            
                            //backingstore to logical memory
                            int temp1 = logicalMemory[ptPointer].pagetable[pageNum].frame;
                            logicalMemory[ptPointer].pagetable[pageNum].frame = lmFreeHead;
                            logicalMemory[ptPointer].pagetable[pageNum].frameType = 1;
                            logicalMemory[ptPointer].pagetable[pageNum].valid = 1;
                            Enqueue(backingStore[temp1].ID,1,tempLRU,2,backingStore[temp1].owner, backingStore[temp1].ptsize,backingStore[temp1].data, backingStore[temp1].pagetable, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory);//change not a pagetable
                            lmSize++;
                            Delete(temp1, &bsHead, &bsTail, &bsFreeHead, &bsFreeTail, backingStore, temp);
                            bsSize--;
                            
							logicalMemory[logicalMemory[ptPointer].pagetable[pageNum].frame].LRU++;
                            
                        }
                        //If there is room in Physical Memory and need to bring from backing store
                        else if(lmSize< 255 && lmSize > 0)
                        {
                            logicalMemory[ptPointer].pagetable[pageNum].frame = lmFreeHead;
                            int tempLRU = logicalMemory[lmFreeHead].LRU;
                            Enqueue(backingStore[logicalMemory[ptPointer].pagetable[pageNum].frame].ID,1,tempLRU,2,backingStore[logicalMemory[ptPointer].pagetable[pageNum].frame].owner, 0,backingStore[logicalMemory[ptPointer].pagetable[pageNum].frame].data, backingStore[logicalMemory[ptPointer].pagetable[pageNum].frame].pagetable, &lmHead, &lmTail, &lmFreeHead, &lmFreeTail, logicalMemory);
                            lmSize++;
                            Delete(logicalMemory[ptPointer].pagetable[pageNum].frame, &bsHead, &bsTail, &bsFreeHead, &bsFreeTail, backingStore, temp);
                            bsSize--;
                            logicalMemory[ptPointer].pagetable[pageNum].frameType = 1;
                            logicalMemory[ptPointer].pagetable[pageNum].valid = 1;
                            
                            logicalMemory[ptPointer].frameType = 2;
							logicalMemory[logicalMemory[ptPointer].pagetable[pageNum].frame].LRU++;
                        }
                        else
                        {
                            error = -1;
                        }
                        
                    }
                    else
                    {
                        error = -2; //frametype is not 1 or 2
                    }
                }
                else if(logicalMemory[ptPointer].pagetable[pageNum].valid ==  1)
                { 
                    //If page is being page faulted again, increment LRU for frame
                    if(logicalMemory[ptPointer].pagetable[pageNum].frameType == 1)
                    {

                        logicalMemory[ptPointer].pagetable[pageNum].valid = 1;
						logicalMemory[logicalMemory[ptPointer].pagetable[pageNum].frame].LRU++;
                    }
                    else
                    {
                        error = -3;//valid bit is 1 and frameType is not
                    }
					
                }
                else
                {
                    error = -4;//valid bit is not 0 or 1
                }
            }
            else
            {
                error = -5;
            }
        }
        
    }
    if(temp == 0)
    {
        error = -6;//pgtable id not found
    }
    
	return error;
}